<?php
/**
 * Copyright (c) 2006-2007, Julien PORTALIER
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright    Copyright (c) 2006-2007, Julien PORTALIER
 * @link         http://featherphp.googlecode.com/
 * @license      http://www.opensource.org/licenses/mit-license.php The MIT License
 */

define('Q_PROCESS_NONE', false);
define('Q_PROCESS_FULL', 1);
define('Q_PROCESS_LITE', 2);
define('Q_PROCESS_LIST', 3);
define('Q_PROCESS_ASSOC_LIST', 4);

/**
 * Base class for database backends.
 */
abstract class db_base
{
	protected $link = null;
	protected $operators;
	public $conf;
	protected $fieldQuote;
	
	protected $debugs = array();
	
	function __construct($conf)
	{
		$this->conf = $conf;
		$this->tablePrefix = $conf['prefix'];
	}
	
	abstract protected function __start();
	abstract protected function __stop();
	abstract protected function error($sql=null);
	
	abstract function useDatabase($database=null);
	abstract function & describeTable($table);
	
	abstract function transactions($com);
	abstract function execute($sql);
	abstract function & select($sql, $process=true, $tableName=0);
	
	abstract function getLastInsertID();
	abstract function value($v);
	abstract function limit($limit, $page=1);
	
	/**
	 * @deprecated Use db_base::select() instead.
	 */
	function & query($sql, $process=true, $tableName=0)
	{
		return $this->select($sql, $process, $tableName);
	}
	
	/**
	 * Quotes a single field.
	 */
	function field($f)
	{
		// do not process field?
		if (strpos($f, '-! ') === 0)
			return str_replace('-! ', '', $f);
		
		$q = $this->fieldQuote;
		
		// operator
		if (stripos($f, 'DISTINCT') !== false)
		{
			$op = 'DISTINCT';
			$f = str_ireplace('DISTINCT', '', $f);
		}
		else {
			$op = '';
		}
		$f = trim($f);
		
		if (strpos($f, '.'))
		{
			// model.field type
			list($m, $f) = explode('.', $f);
			$f = ($f != '*') ? "$q$m$q.$q$f$q" : "$q$m$q.$f";
		}
		else
		{
			// simple field
			$f = ($f != '*') ? "$q$f$q" : $f;
		}
		
		return "$op $f";
	}
	
	/**
	 * Parses a conditions' array.
	 *
	 * @param  $conditions  array
	 * @param  $operator    string AND | OR
	 */
	function conditions(array $conditions, $operator='AND', $not='')
	{
		if (empty($conditions))
			return;
		
		if (is_array($conditions))
		{
			if (!$this->link)
				$this->__start();
#			$conditions = implode(" $operator ", $_conditions);
			$conditions = $this->_conditions($conditions, $operator, $not);
		}
		
		return "WHERE $conditions";
	}
	
	protected function _conditions(array $conditions, $operator='AND', $not='', $index=0)
	{
		$_conditions = '';
		foreach ($conditions as $f => $v)
		{
			$_operator = $operator;
			if ($f === 'NOT')
				$_condition = $this->_conditions($v, $operator, 'NOT');
			elseif ($f === 'AND' or $f === 'OR')
			{
				$_operator = $f;
				$_condition = $this->_conditions($v, $operator, $not);
			}
			else
				$_condition = $this->condition($f, $v, $not);
			
			if ($index > 0)
				$_conditions .= " $_operator ".$_condition;
			else
			{
				$_conditions .= $_condition;
				$index++;
			}
		}
		return $_conditions;
		/*
		$_conditions = array();
		foreach ($conditions as $f => $v)
		{
			if ($f === 'NOT')
				$_conditions[] = $this->_conditions($v, $operator, 'NOT');
			elseif ($f === 'AND' or $f === 'OR')
				$_conditions[] = $this->_conditions($v, $f, $not);
			else
				$_conditions[] = $this->condition($f, $v, $not);
		}
		return $_conditions;
		#return implode(" $operator ", $_conditions);
		*/
	}
	
	/**
	 * Parses a single condition.
	 *
	 * @param  $f    string  Field.
	 * @param  $v    string  Value.
	 */
	function condition($f, $v, $not='')
	{
		$_f = $this->field($f);
		
		# [NOT] IN
		if (is_array($v))
		{
			$str = array();
			
			foreach($v as $k => $vv)
			{
				if (strtoupper($k) != 'NOT')
				{
					if (!is_array($vv))
						$v[$k] = $this->value($vv);
					else
					{
						# IN
						array_push($str, $this->condition($f, $vv));
						unset($v[$k]);
					}
				}
				else
				{
					# NOT IN
					array_push($str, $this->condition($f, $vv, 'NOT'));
					unset($v[$k]);
				}
			}
			
			if (empty($str))
				$str = '';
			else
			{
				$str = implode(" AND ", $str);
				if (empty($v))
					return $str;
				$str .= " AND ";
			}
			
			return "$str$_f $not IN ( ".implode(', ', $v)." )";
		}
		
		# don't process value?
		if (strpos($v, '-! ') === 0)
		{
			$v = str_replace('-! ', '', $v);
			return "$_f $not $v";
		}
		
		# specified (or not) operator
		if (!preg_match("/^({$this->operators}) .+/i", $v))
			$oper = '=';
		else
			list($oper, $v) = explode(' ', $v, 2);
		
		$v = $this->value($v);
		return "$_f $not $oper $v";
	}
	
	/**
	 * Quotes and secures a list of fields.
	 * 
	 * @param  $fields  mixed  Array or comma separated list.  
	 */
	function fields($fields)
	{
		if (!is_array($fields))
		{
			if (strpos($fields, '-! ') !== false)
				return str_replace('-! ', '', $fields);
			$fields = explode(',', $fields);
		}
		
		$_f = array();
		
		foreach ($fields as $f)
			array_push($_f, $this->field($f));
		
		return implode(', ', $_f);
	}
	
	/**
	 * Quotes and secures a list of values.
	 * 
	 * @param  $fields  mixed  Array or comma separated list.  
	 */
	function values(array $values)
	{
		$arr = array();
		foreach($values as $k => $v)
			$arr[$k] = $this->value($v);
		return implode(', ', $arr);
	}
	
	/**
	 * Quotes an ORDER BY request.
	 */
	function order($order, $type='ORDER')
	{
		if (strpos($order, '-! ') === 0)
			return substr($order, 3);
		elseif (strpos($order, 'RAND()') === false)
			$order = preg_replace_callback('/([\w.]+)\s*(ASC|DESC|)[\s,]*/', array($this, '__preg_order'), $order);
		return "$type BY $order";
	}
	
	/**
	 * Quotes a GROUP BY request.
	 */
	function groupby($groupby)
	{
		return $this->order($groupby, 'GROUP');
	}
	
	protected function __preg_order($m)
	{
		return str_replace($m[1], $this->field($m[1]), $m[0]);
	}
	
	/**
	 * Displays list of queries at the end of the page (when in SQL DEBUG mode).
	 */
	function __destruct()
	{
		$this->__stop();
		
		# no debug?
		if (DEBUG < 2 or (defined('DB_NO_DEBUG') and DB_NO_DEBUG))
			return;
		
		if (isset($_SERVER['SERVER_ADDR']))
		{
			echo '<table class="DBDebug" summary="SQL queries">' .
				'<tr>' .
				'<th>['.$this->conf['driver'].'] queries ( '.count($this->debugs).' )</th>' .
				'<th>error</th>' .
				'<th>affected</th>' .
				'<th>time</th>' .
				'</tr>';
			
			foreach($this->debugs as $d)
			{
				printf("\n<tr>" .
					"<td>%s</td>" .
					"<td>%s</td>" .
					"<td>%d</td>" .
					"<td>%.02fms</td>" .
					"</tr>",
					$d['sql'], $d['error'], $d['affected'], ($d['time'] * 1000));
			}
			echo '</table>';
		}
		else
		{
			foreach($this->debugs as $d)
			{
				printf("\n> %s\n".
					"Affected: %d - Time: %.02fms\n".
					"Error: %s\n",
					$d['sql'], $d['affected'], ($d['time'] * 1000), $d['error']);
			}
		}
	}
	
	/**
	 * Generates a SQL SELECT query.
	 */
	function prepareSelect(array &$data)
	{
		$from   = "";
		$fields = "*";
		$where  = null;
		$order  = null;
		$group  = null;
		$limit  = null;
		$joins  = "";
		
		foreach($data as $k => $v)
		{
			switch($k)
			{
				case 'fields': $fields  = $this->fields($v);     break;
				case 'where':  $where   = $this->conditions($v); break;
				case 'order':  $order   = $this->order($v);   break;
				case 'group':  $group   = $this->groupby($v); break;
				case 'from':   $from    = $this->field($v); break;
				case 'join':
					if (!isset($v[0]))
						$v = array($v);
					
					foreach($v as $j)
					{
						$type   = isset($j['type']) ? $j['type'] : 'LEFT';
						$table  = $this->field($j['table']);
						$left   = $this->field($j['left']);
						$right  = $this->field($j['right']);
						$joins .= "$type JOIN $table ON $left = $right ";
					}
					break;
				case 'limit':
					$limit = $this->limit($v, isset($data['page']) ? $data['page'] : null);
					unset($data['page']);
					break;
				default:
					if ($k != 'page' and DEBUG) {
						trigger_error("Unknown parameter: $k", E_USER_WARNING);
					}
			}
		}
		
		return "SELECT $fields FROM $from $joins $where $order $group $limit;";
	}
	
	/**
	 * SQL INSERT.
	 */
	function insert($table, array &$data)
	{
		$table  = $this->field($table);
		$fields = $this->fields(array_keys($data));
		$values = $this->values(array_values($data));
		return $this->execute("INSERT INTO $table ( $fields ) VALUES ( $values ) ;");
	}
	
	/**
	 * SQL REPLACE.
	 */
	function replace($table, array &$data)
	{
		$table  = $this->field($table);
		$fields = $this->fields(array_keys($data));
		$values = $this->values(array_values($data));
		return $this->execute("REPLACE INTO $table ( $fields ) VALUES ( $values ) ;");
	}
	
	/**
	 * SQL UPDATE.
	 */
	function update($table, $where, array &$data)
	{
		$table = $this->field($table);
		$where = $this->conditions($where);
		
		# data
		$values = array();
		foreach($data as $f => $v)
			$values[] = $this->field($f).' = '.$this->value($v);
		$values = implode(', ', $values);
		
		# query
		return $this->execute("UPDATE $table SET $values $where ;");
	}
	
	/**
	 * SQL DELETE.
	 */
	function delete($table, $where=null)
	{
		if (!empty($where))
			$where = $this->conditions($where);
		$table = $this->field($table);		
		return $this->execute("DELETE FROM $table $where ;");
	}
}
?>